home *** CD-ROM | disk | FTP | other *** search
/ Turnbull China Bikeride / Turnbull China Bikeride - Disc 2.iso / BARNET / PROGRAMMING / DOCS / TOPTIPS < prev    next >
Text File  |  1998-06-02  |  12KB  |  413 lines

  1. <html>
  2. <head> <title> Top Tips on programming in ARM Assembler </title> </head>
  3. <body>
  4.  
  5. This page demonstrates some of the optimisations which are possible when
  6. programming in ARM assembler.  It's based on my experience of optimising
  7. other people's code, so it's real examples of tricks that people overlook.
  8. This page assumes a familiarity with the ARM instruction set, and
  9. programming in assembler in general.
  10.  
  11. <h1> Use of conditionals </h1>
  12.  
  13. Conditional execution of instructions is one of the best things about the
  14. ARM instruction set, and yet people overlook it so often.  For example,
  15. returning with the V bit set from a function is often used to indicate
  16. an error occurred:
  17.  
  18. <pre>
  19.     STMFD    r13!, {r14}
  20.     BL    func1
  21.     LDMVSFD    r13!, {pc}
  22.     ADD    r0, r1, r2
  23.     BL    func2
  24.     LDMVSFD    r13!, {pc}
  25.     SUB    r1, r2, r0
  26.     BL    func3
  27.     LDMVSFD    r13!, {pc}
  28.     LDMFD    r13!, {pc}^
  29. </pre>
  30. can be rewritten as:
  31. <pre>
  32.     STMFD    r13!, {r14}
  33.     BL    func1
  34.     ADDVC    r0, r1, r2
  35.     BLVC    func2
  36.     SUBVC    r1, r2, r0
  37.     BLVC    func3
  38.     LDMVCFS    r13!, {pc}^
  39.     LDMFD    r13!, {pc}
  40. </pre>
  41.  
  42. A saving of 8 bytes in space, and 3 instructions in execution time for the
  43. case when no errors occur - which is hopefully the more common!  I also
  44. find it more readable and it's easier to trace the flow of execution.
  45. Naturally, if you have to do comparisons then this is not possible and
  46. you'll have to either exit or branch over the conditional segment of code.
  47. It can be more efficient to do this if you have a long procedure exit
  48. sequence, but normally exiting is merely a matter of unstacking the
  49. registers and writing the appropriate value to the program counter which
  50. is one instruction.
  51.  
  52. <h1> Use of the correct comparison </h1>
  53.  
  54. The different types of comparison available often confuse the novice
  55. programmer.  Use of signed comparisons instead of unsigned is the most
  56. common error which is made, which can be merely inefficient (and
  57. <a href="#final">here's an example</a>), but often leads to bugs when
  58. memory ranges are suddenly greater than 2GB.
  59.  
  60. <pre>
  61.   Unsigned   Signed
  62.    HS (CS)    GE
  63.    HI         GT
  64.    LS         LE
  65.    LO (CC)    LT
  66. </pre>
  67.  
  68. PL and MI directly test bit 31 of the result (ie the N flag is a copy of
  69. bit 31).  They are subtly different to GE and LT and I'd like someone to
  70. give me a case where they should be used instead of GE or LT.
  71.  
  72. <h1> Don't misuse the stack </h1>
  73.  
  74. Obviously, don't stack registers that aren't required to be preserved -
  75. which these are depends on whether you are writing APCS conforming code,
  76. or whether you have your own Procedure Call Standard, or whether you work
  77. on an ad hoc basis.  But it's not necessary to stack r14 if the function
  78. is a leaf function (calls no other functions).  However, if you do find
  79. yourself in need of more registers than you have available, then r14
  80. should be the first register you stack on the grounds of efficiency.
  81. Consider the two functions:
  82.  
  83. <pre>
  84.     STMFD    r13!, {r14}
  85.     MUL    r0, r1, r2
  86.     LDMFD    r13!, {pc}
  87.  
  88.     STMFD    r13!, {r1, r2}
  89.     LDR    r1, [r12, #8]
  90.     LDR    r2, [r12, #12]
  91.     MUL    r0, r1, r2
  92.     LDMFD    r13!, {r1, r2}
  93.     MOVS    pc, r14
  94. </pre>
  95.  
  96. Better would be:
  97.  
  98. <pre>
  99.     MUL    r0, r1, r2
  100.     MOVS    pc, r14
  101.  
  102.     STMFD    r13!, {r14}
  103.     LDR    r14, [r12, #8]
  104.     LDR    r0, [r12, #12]
  105.     MUL    r0, r14, r0
  106.     LDMFD    r13!, {pc}
  107. </pre>
  108.  
  109. In the second function, restoring registers is combined with returning
  110. from the procedure call.  Since r14 must be corrupted by the function
  111. call, the caller is not expecting its value to be any particular value at
  112. the end of the function.  This has been exploited in at least one program
  113. which I know of to return function values in r14, but you really need
  114. to be aware of exactly what you're doing with that technique, and I've
  115. never dared try it myself.
  116. <p>
  117. A further optimisation which can be used is to use a single store rather
  118. than a multiple store if you're only storing r14.  The above code then
  119. becomes:
  120.  
  121. <pre>
  122.     STR    r14, [r13, #-4]!
  123.     LDR    r14, [r12, #8]
  124.     LDR    r0, [r12, #12]
  125.     MUL    r0, r14, r0
  126.     LDR    pc, [r13], #4
  127. </pre>
  128.  
  129. Beware that you can't tell LDR to restore the program counter flags.
  130. This is not an issue if you are using 32-bit APCS-3, but most routines
  131. on 26-bit ARMs preserve the flags over a function call.
  132.  
  133. <p>
  134. It can sometimes be a win to only stack registers conditionally.  Here's
  135. an example:
  136.  
  137. <pre>
  138.     LDR    r2, [r12, #0]
  139.     TEQ    r0, r2
  140.     MOVEQ    pc, r14
  141.     STMFD    r13!, {r14}
  142.     BL    func
  143.     SUB    r0, r0, #1
  144.     LDMFD    r13!, {pc}
  145. </pre>
  146.  
  147. This can be more efficient if r0 is often equal to r2; for example if this
  148. were an error check.
  149.  
  150. <p>
  151. Another way in which the stack can be abused is to store data _below_ the
  152. stack pointer.  This will appear to work but you will get random crashes.
  153. This is because interrupt code is entitled to use space on the stack
  154. temporarily; if you have left the stack pointer in the wrong place, you
  155. will have problems.
  156.  
  157. <h1> References to data within a big program </h1>
  158.  
  159. When your program gets to more than 4k, you start to run the risk of
  160. not being able to address data.  This is because LDR has a maximum
  161. range of +/-4095 bytes.  ADR (which is a pseudoinstruction for
  162. ADD rN, pc, x, which takes into account the fact that
  163. the program counter is 2 instructions ahead of the currently executing
  164. instruction) uses the normal 8 bits rotated by a multiple of 2 scheme,
  165. so this can get you further away but at the cost of not being able to
  166. directly access every address.  Many people have FNadrl macros and at
  167. least two assemblers that I know of have ADRL instructions.  They are
  168. implemented as something like:
  169.  
  170. <pre>
  171.     ADR rN, (offset AND &FF)
  172.     ADD rN, ((offset - P%)AND &FF00)
  173. </pre>
  174.  
  175. with slight modifications to cope with a negative offset, and the cunning
  176. ones actually try various possibilities such as
  177.  
  178. <pre>
  179.     ADR rN, (offset AND &3FC)
  180.     ADD rN, ((offset - P%) AND &3FC00)
  181. </pre>
  182.  
  183. to gain them more distance.
  184.  
  185. <p>
  186. Nevertheless, these pseudo-instructions take 2 instructions, and ADRX
  187. (for constants which can't be reached with ADRL) takes 3.  It is better
  188. to avoid using these if possible.  Try moving data around so that it's
  189. nearer the functions which reference it.  If that's not possible, try
  190. moving functions around.
  191.  
  192. <p>
  193. If you have data which are used throughout the program, consider
  194. allocating a register to point to their address throughout the execution
  195. of the program.  In RISC OS modules, this is assisted by Acorn by pointing
  196. r12 at a block of private workspace.
  197.  
  198. <p>
  199. Under no circumstances consider doing the following:
  200.  
  201. <pre>
  202.     BL    getvar
  203. ...
  204. .getvar
  205.     LDR    r0, var
  206.     MOVS    pc, r14
  207. .var
  208.     EQUD    0
  209. </pre>
  210.  
  211. since this involves two pipeline flushes and wrecks most caching
  212. strategies so it is guaranteed to be slower.  It also goes against the
  213. principle of keeping data and code separate which is more important on
  214. StrongARM processors with their split instruction/data caches.
  215.  
  216. <h1> Working with large constants </h1>
  217.  
  218. By large constants, I don't mean constants with a large magnitude like 2^31,
  219. I mean constants which don't fit in the ARM's scheme for immediate constants;
  220. ie 8 bits rotated by a multiple of 2.  This example is from IscaFS:
  221.  
  222. <pre>
  223.     LDR    r0, [r6, #24]
  224.     BIC    r0, r0, #&ff000000
  225.     BIC    r0, r0, #&00ff0000
  226.     MOV    r1, r1, #&53
  227.     ORR    r1, r1, #&ef00
  228.     TEQ    r0, r1
  229. </pre>
  230.  
  231. I replaced this with:
  232.  
  233. <pre>
  234.     LDR    r0, [r6, #24]
  235.     MOV    r0, r0, LSL #16
  236.     EOR    r0, r0, #&ef000000
  237.     TEQ    r0, #&00530000
  238. </pre>
  239.  
  240. Shifting r0 left by 16 automatically zeroes the leftmost 16 bits
  241. and discards the previous top 16 bits, which is the desired effect.
  242. The crucial step is noticing that TEQ is the same as EORS, without a
  243. destination register.  So if the top byte of r0 is not &ef then the
  244. second test can never be true.  This trick saves 2 instructions and
  245. one register.
  246.  
  247. <p> A similar technique can apply to other situations, for example
  248. checking that a 16 bit value is less than a given value can be done
  249. by
  250.  
  251. <pre>
  252.     CMP    r0, #&xy00
  253.     CMPEQ    r0, #&00za
  254. </pre>
  255.  
  256. is the same as
  257.  
  258. <pre>
  259.     MOV    r1, #&xy00
  260.     ORR    r1, #&00za
  261.     CMP    r0, r1
  262. </pre>
  263.  
  264. but takes one instruction fewer and uses one register fewer.
  265. <p>
  266. If it's utterly unavoidable, you may need to put a large constant into a
  267. register.  It is thought that there are no constants which cannot be
  268. produced in 3 instructions, though no-one has an algorithm for producing
  269. arbitrary constants (to the best of my knowledge).  On a StrongARM, it
  270. is normally quicker to load an instruction instead of using 3 to synthesise
  271. it.  On an ARM6, it is quicker to use 3 instructions.  Take your pick.
  272.  
  273. <h1> Strength reduction </h1>
  274.  
  275. This term covers a number of optimisations.  One is to reduce the cost of
  276. per-iteration calculations.
  277.  
  278. <pre>
  279.     MOV    r0, #200
  280.     MOV    r4, #8
  281. .loop
  282.     MUL    r1, r0, r4
  283.     ...
  284.     SUBS    r0, r0, #1
  285.     BNE    loop
  286. </pre>
  287.  
  288. becomes
  289.  
  290. <pre>
  291.     MOV    r1, #1600
  292. .loop
  293.     ...
  294.     SUBS    r1, r1, #8
  295.     BNE    loop
  296. </pre>
  297.  
  298. This saves a MUL instruction in the loop, which is a slow operation on many
  299. variants of the ARM.  In this case, it also saves 2 registers, though in
  300. practice this is not often achieved.
  301.  
  302.  
  303. <h1> Count down, not up </h1>
  304.  
  305. The above example also illustrates that it's better to count down in a loop
  306. than up.  Here's a worse example:
  307.  
  308. <pre>
  309.     MOV    r1, #0
  310. .loop
  311.     ...
  312.     ADD    r1, r1, #8
  313.     CMP    r1, #1600
  314.     BNE    loop
  315. </pre>
  316.  
  317. The SUBS used above combines the test-for-end with the loop-count, saving
  318. an instruction.
  319.  
  320.  
  321. <h1> Unrolling loops </h1>
  322.  
  323. Since the instructions used per-loop are not doing useful work, it is often
  324. better to unroll the loop a little.  This can add extra overhead in places,
  325. so you should be cautious.  It may also pay to unroll the loop entirely, as
  326. the C compiler may sometimes do with memset.  To reuse the example above:
  327.  
  328. <pre>
  329.     MOV    r1, #1600
  330. .loop
  331.     ...
  332.     SUB    r1, r1, #8
  333.     ...
  334.     SUBS    r1, r1, #8
  335.     BNE    loop
  336. </pre>
  337.  
  338. In this example, since I haven't specified what is in `...', it's not
  339. possible to combine the first SUB in with it which might be possible in
  340. real code.  This optimistaion saves half an instruction (and a cache line flush) per loop
  341. - 800 in total.  Against that, it takes more cache lines, and more RAM in
  342. general.  The only way to find if this is a win or not is to benchmark the
  343. code in question.
  344.  
  345.  
  346. <h1> Dividing </h1>
  347.  
  348. There are already several good algorithms out there and I
  349. don't think I have anything to contribute myself.  Please see <a
  350. href="http://www.comlab.ox.ac.uk/oucl/users/robin.watts/Docs/Arc/Programmin/div-fast.html">
  351. This page</a> and <a
  352. href="http://www.comlab.ox.ac.uk/oucl/users/robin.watts/Docs/Arc/Programmin/div-short.html">
  353. this one</a> for good examples.
  354.  
  355.  
  356. <h1> Some further examples </h1>
  357.  
  358. That's about all the coding tricks I can remember for the moment.  If you
  359. want to see a real example of code which I've improved, try looking at
  360. IscaFS which was originally written by Phil Norman.
  361.  
  362. <p>
  363.  
  364. I'll leave you with some more fictional examples:
  365.  
  366. <a name="final"></a>
  367. <pre>
  368.     STMFD    r13!, {r14}
  369.     CMP    r0, #5
  370.     BGT    not
  371.     CMP    r0, #0
  372.     BLT    not
  373.     LDR    r0, [r12, #8]
  374.     B    over
  375. .not
  376.     MOV    r0, #0
  377.     SUB    r0, r0, #1
  378. .over
  379.     LDMFD    r13!, {pc}
  380. </pre>
  381.  
  382. Can be optimised to:
  383.  
  384. <pre>
  385.     CMP    r0, #5
  386.     LDRLS    r0, [r12, #8]
  387.     MVNHI    r0, #0
  388.     MOV    pc, r14
  389. </pre>
  390.  
  391. This example demonstrates several points:
  392. <ol>
  393. <li>    Use of unsigned instead of signed comparisons
  394. <li>    Use of conditionals to avoid branches
  395. <li>    Not storing the link register if avoidable
  396. <li>    Remembering one of the more `esoteric' instructions
  397. </ol>
  398.  
  399. Thanks for reading, I appreciate feedback and if you have any tricks you'd
  400. like to share with the ARM programming community at large then please send
  401. them to me.
  402. <p>
  403. You probably want to look at these pages for further tips:
  404. <a href="http://www.comlab.ox.ac.uk/oucl/users/robin.watts/Docs/">Robin
  405. Watts' ARM programming page</a>
  406. <p>
  407. I'd like to thank Phil Norman and Peter Burwood for their help.
  408.  
  409. <hr>
  410. <i><a href="mailto:willy@bofh.ai">Matthew Wilcox</a></i>
  411. </body>
  412. </html>
  413.